package hell.satan.droidbdf.utils;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.SparseArray;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Cerberus
 */
@SuppressWarnings("unused")
public class FileUtils {
    protected static final String TAG = FileUtils.class.getSimpleName();

    /**
     * 复制文件（夹）
     * Copy file(s)
     *
     * @param src      源文件
     *                 File to be copied
     * @param dst      目标文件
     *                 File to be copy to
     * @param preserve 是否保留最后访问时间
     *                 Keep last modified time
     * @return true表示复制成功。 True if copy successfully
     */
    public static boolean copy(@NonNull File src, @NonNull File dst, boolean preserve) {
        try {
            doCopy(src, dst, preserve);
            return true;
        } catch (IOException ex) {
            LogWrapper.w(TAG, ex);
        }
        return false;
    }

    /**
     * 复制文件（夹）
     * Copy file(s)
     *
     * @param src      源文件路径
     *                 Path to be copied
     * @param dst      目标文件路径
     *                 Path to be copy to
     * @param preserve 是否保留最后访问时间
     *                 Keep last modified time
     * @return true表示复制成功。 True if copy successfully
     */
    public static boolean copy(@NonNull String src, @NonNull String dst, boolean preserve) {
        return copy(new File(src), new File(dst), preserve);
    }

    /**
     * 复制文件（夹）
     * Copy file(s)
     *
     * @param src 源文件
     *            File to be copied
     * @param dst 目标文件
     *            File to be copy to
     * @return true表示复制成功。 True if copy successfully
     */
    public static boolean copy(@NonNull File src, @NonNull File dst) {
        return copy(src, dst, true);
    }

    /**
     * 复制文件（夹）
     * Copy file(s)
     *
     * @param src 源文件路径
     *            Path to be copied
     * @param dst 目标文件路径
     *            Path to be copy to
     * @return true表示复制成功。 True if copy successfully
     */
    public static boolean copy(@NonNull String src, @NonNull String dst) {
        return copy(new File(src), new File(dst));
    }

    /**
     * 删除文件（夹）
     * Delete file or directory
     *
     * @param src 待删除文件
     *            File to be deleted
     * @return true表示删除成功。 True if delete successfully
     */
    public static boolean delete(@NonNull File src) {
        if (src.isDirectory()) {
            File[] subs = src.listFiles();
            for (File subsrc : subs) {
                if (!delete(subsrc)) return false;
            }
        }
        return src.delete();
    }

    /**
     * 删除文件（夹）
     * Delete file or directory
     *
     * @param src 待删除文件路径
     *            Path to be deleted
     * @return true表示删除成功。 True if delete successfully
     */
    public static boolean delete(@NonNull String src) {
        return delete(new File(src));
    }

    /**
     * 移动文件（夹）
     * Move file(s)
     *
     * @param src 源文件
     *            File to be moved
     * @param dst 目标文件
     *            File to be move to
     * @return true表示移动成功。 True if move successfully
     */
    public static boolean move(@NonNull File src, @NonNull File dst) {
        return !src.renameTo(dst) && copy(src, dst) && delete(dst);
    }

    /**
     * 移动文件（夹）
     * Move file(s)
     *
     * @param src 源文件路径
     *            Path to be moved
     * @param dst 目标文件路径
     *            Path to be move to
     * @return true表示移动成功。 True if move successfully
     */
    public static boolean move(@NonNull String src, @NonNull String dst) {
        return move(new File(src), new File(dst));
    }

    public static String fixSlash(String path) {
        while (path.contains("//") || path.contains("/./") || path.contains("/../")) {
            //去掉重复的路径分隔符
            path = path.replace("//", "/");
            //去掉当前路径标识符
            path = path.replace("/./", "/");
            //去掉父路径标识符
            path = path.replaceFirst("(:|/)[^/]*/\\.\\./", "$1");
        }
        return path;
    }

    /**
     * 查找第一个存在的文件
     * Find first exist file.
     *
     * @param files 将要被查找的文件
     *              The files to find
     * @return 查找到的第一个存在的文件，可能为空。
     * First existing file, null if not found
     */
    @Nullable
    public static File findExist(File[] files) {
        for (File file : files) {
            if (file.exists()) return file;
        }
        return null;
    }

    /**
     * 查找第一个存在的路径
     * Find first exist path.
     *
     * @param paths 将要被查找的路径
     *              The paths to find
     * @return 查找到的第一个存在的路径，可能为空。
     * First existing path, null if not found
     */
    @Nullable
    public static String findExist(String... paths) {
        for (String path : paths) {
            if (new File(path).exists()) return path;
        }
        return null;
    }

    /**
     * 查找第一个存在的路径，使用方法：
     * Find first exist path. How to use:
     * <p>
     * path => /folder/{1|2}/file.{a|b|c}
     * <table>
     * <tr><th>order</th><th>path</th><th>exist</th></tr>
     * <tr><td>0</td><td>/folder/1/file.a</td><td>no</td></tr>
     * <tr><td>1</td><td>/folder/1/file.b</td><td>no</td></tr>
     * <tr><td>2</td><td>/folder/1/file.c</td><td>yes</td></tr>
     * <tr><td>3</td><td>/folder/2/file.a</td><td>yes</td></tr>
     * <tr><td>4</td><td>/folder/2/file.b</td><td>no</td></tr>
     * <tr><td>5</td><td>/folder/2/file.c</td><td>yes</td></tr>
     * </table>
     * return => /folder/1/file.c
     * </p>
     *
     * @param path 将要被查找的路径模式
     *             The path patten to find
     * @return 查找到的第一个存在的路径，可能为空。
     * First existing path, null if not found
     */
    @Nullable
    public static String findExist(String path) {
        SparseArray<String[]> parts = new SparseArray<>();
        StringBuffer sb = new StringBuffer();
        Pattern pattern = Pattern.compile("(?:(?<!\\\\)\\{(.+?)(?<!\\\\)\\})");
        Matcher matcher = pattern.matcher(path.replaceAll("@", "@@"));
        while (matcher.find()) {
            matcher.appendReplacement(sb, "@" + parts.size() + "@");
            parts.put(parts.size(), matcher.group(1).split("(?<!\\\\)\\|"));
        }
        matcher.appendTail(sb);
        String encode = sb.toString().replaceAll("\\\\", "");

        int count = 1;
        for (int i = 0; i < parts.size(); i++) {
            count *= parts.get(i).length;
        }
        for (int i = 0; i < count; i++) {
            String find = encode;
            int index = i;
            for (int j = parts.size() - 1; j >= 0; j--) {
                String[] array = parts.get(j);
                find = find.replaceAll("(?<!@)@" + j + "@", array[index % array.length]);
                index = index / array.length;
            }
            find = fixSlash(find.replaceAll("@@", "@"));
            File file = new File(find);
            if (file.exists()) return find;
        }
        return null;
    }

    protected static void doCopy(File src, File dst, boolean preserve) throws IOException {
        if (src.isDirectory()) {
            //noinspection ResultOfMethodCallIgnored
            dst.mkdirs();
            File[] subs = src.listFiles();
            for (File subsrc : subs) {
                File subdst = new File(dst, subsrc.getName());
                doCopy(subsrc, subdst, preserve);
            }
        } else {
            //noinspection ResultOfMethodCallIgnored
            dst.createNewFile();
            FileInputStream fis = null;
            FileChannel fic = null;
            FileOutputStream fos = null;
            FileChannel foc = null;
            try {
                fis = new FileInputStream(src);
                fic = fis.getChannel();
                fos = new FileOutputStream(dst);
                foc = fos.getChannel();
                long size = fic.size();
                long position = 0;
                do {
                    position += foc.transferFrom(fic, position, Constants.SIZE_MB);
                } while (position < size);
            } finally {
                IOUtils.closeQuietly(foc);
                IOUtils.closeQuietly(fos);
                IOUtils.closeQuietly(fic);
                IOUtils.closeQuietly(fis);
            }
        }

        if (preserve) {
            //noinspection ResultOfMethodCallIgnored
            dst.setLastModified(src.lastModified());
        }
    }
}
